/*
 * PTXExecution.cpp
 *
 * This source file implements operations defined
 * in PTXExecution.h.
 */
#include <cuda/cuda.h>
#include <stdlib.h>
#include <string.h>
#include <iostream>

#include "g_crossbit/PTXExecution.h"
#include "g_crossbit/PTXTranslator.h"

using namespace std;
using namespace g_crossbit;

//#define DEBUG

/*
 * Constructor
 *
 * @file_name: a file whose contents is ptx instruction.
 * @cur_state: current processor's state.   
 */
PTXExecution::PTXExecution (unsigned int spc, IA32ProcessorState *cur_state)
{
   this->cur_state = cur_state;
   produceName (spc, 0); /* produce a name of ptx file. */
   produceName (spc, 1); /* produce a name of record file. */

#ifdef DEBUG
   printf ("ptx_fn: %s\n", ptx_fn);
   printf ("record_fn: %s\n", record_fn);
#endif
   Record::loadRecords (record_fn, cur_state);
   records_ptr = Record::record_ptr;
}

/*
 * Produce a name of file.
 *
 * Parameters
 * @spc : address of an instruction.
 * @flag: 0 if this function wants to produce a name of ptx file;
 *        1 if this function wants to produce a name of record file.
 *
 * Return : null
 */

void
PTXExecution::produceName (unsigned int spc, short flag)
{
   int  len;
   char *path, *ptr;
   
   if ( (path = getenv ("SCRIPT_D")) == NULL) {
   	  printf ("env named SCRIPT_D doesn't exist.\n");
	  exit (0);
   }
  
#ifdef DEBUG
   printf ("The length of SCRIPT_D is %d\n", strlen (path));
   printf ("SCRIPT_D is %s\n", path);
#endif
   /* Generate the absolute path of the ptx file or the record file. */
   len = strlen (path);
   ptr = (flag == 0) ? ptx_fn : record_fn;
   strncpy (ptr, path, len); 
   path = ptr;
   
   ptr += len;
   *ptr++ = '/';
   if (flag == 0) {
	   *ptr++ = 'P';
	   *ptr++ = 'T';
	   *ptr++ = 'X';
      *ptr = '\0';
   } 
   else {
      *ptr++ = 'R';
      *ptr++ = 'e';
      *ptr++ = 'c';
      *ptr++ = 'o';
      *ptr++ = 'r';
      *ptr++ = 'd';
      *ptr++ = 's';
      *ptr = '\0';
   }
   
   ptr = path + strlen (path);
   *ptr++ = '/';
   *ptr = '\0';
#ifdef DEBUG
   printf ("Until now, path is %s\n", path);
#endif
   /* 
    * Write spc in the form of hex into array 
    * pointed by ptr. 
    */
   len = sizeof (ptx_fn) - strlen (path);
   snprintf (ptr, len, "0x%08x", spc);
   ptr += 10;

   if (flag == 0)
      snprintf (ptr, len - 10, "%s", ".ptx");
   else 
      snprintf (ptr, len - 10, "%s", ".records");
}

/*
 * The responsibility of this function is getting data that
 * a specified ptx file needs ready, setting up running
 * environment , loading the specified ptx file into GPU's 
 * memory and running it.
 *
 * Parameters: null
 * Return    : null
 */

void
PTXExecution::run ()
{
#ifdef DEBUG
   printf ("At the head of run().\n");
   printf ("PTX file: %s\n", ptx_fn);
#endif

   // PTXTranslator
   // Added by Dylan, 2009-12-26
   PTXTranslator ptx_translator;
   char path[100], *psd = NULL;
   string ptx_in, ptx_out;
   
   if ( (psd = getenv ("SCRIPT_D")) == NULL) {
   	  printf ("env named SCRIPT_D doesn't exist.\n");
	  exit (0);
   }

   strcpy(path, psd);
   strcat(path, "/IR/text_vblock");
   ptx_in.assign(path);
   ptx_out.assign(ptx_fn);
   // Edit by Dylan 1.20
   ptx_translator.TransEngine(records_ptr, ptx_in, ptx_out);

   if (cuInit (0) != CUDA_SUCCESS) {
	  printf ("There is no CUDA driver.\n");
	  exit (0);
   }
   
   /* Get the number of devices that supports CUDA. */
   int deviceCount = 0;
   cuDeviceGetCount (&deviceCount);
   if (deviceCount == 0) {
      printf ("This is no device that supports CUDA.\n");
      exit (0);
   }

#ifdef DEBUG
   printf ("Here: 117\n");
#endif
   /* Get handle for device 0. */
   CUdevice driver = 0;
   cuDeviceGet (&driver, 0);

#ifdef DEBUG
   printf ("Here: 125\n");
#endif

   /* Create context. */
   CUcontext context;
   cuCtxCreate (&context, 0, driver);

#ifdef DEBUG
   printf ("Here: 133\n");
#endif

   CUevent start, stop;
   cuEventCreate (&start, CU_EVENT_DEFAULT);
   cuEventCreate (&stop , CU_EVENT_BLOCKING_SYNC);
   cuEventRecord (start, 0);

#ifdef DEBUG
   printf ("Here: 142\n");
#endif

   /* Load a ptx file into GPU's memory. */
   CUmodule module;
#ifdef DEBUG
   printf ("ptx_fn: %s\n", ptx_fn);
#endif
   int result = cuModuleLoad (&module, ptx_fn);

#ifdef DEBUG
   printf ("result = %d\n", result);
   if (result == 0)
   	  printf ("151 is normal.\n");
#endif

   /* Identify the entry of the ptx file. */
   CUfunction function;
   result = cuModuleGetFunction (&function, module, "MatrixMulKernel");

#ifdef DEBUG
   if (result == 0)
   	  printf ("160 is normal.\n");
#endif

   void *ptr; 
   CUdeviceptr d_gen, *d_n;
   unsigned int start_addr;
   int length, offset = 0;
   int height_a, width_b, data_type;
   int records_in  = Record::record_in;
   int records_num = Record::record_num;
	
   /* Allocating memory for d_n */
   if( (d_n = new CUdeviceptr[records_num]) == NULL) {
      std::cout << "There is not enough memory.\n";
      exit (0);
   }

   /* 
	* copy the values of eigth general registers
	* from main memory to GPU's global memory.
	*/
   start_addr = cur_state->get (0);
   cuMemAlloc (&d_gen, 32);
   cuMemcpyHtoD (d_gen, (int *)start_addr, 32);
   ptr = (void **)(size_t) d_gen;
   ALIGN_OFFSET (offset, __alignof (ptr));
   cuParamSetv (function, offset, &ptr, sizeof (ptr));
   offset += sizeof (ptr);

   for (int i = 0; i < records_num; i++) {
      start_addr = records_ptr[i].getStartAddr ();
      length   = records_ptr[i].getLength ();
      /* Allocate memory in GPU. */
      cuMemAlloc (&(d_n[i]), length);
 
      /* Load data from main memory to GPU's memory. */
      cuMemcpyHtoD (d_n[i], (void *)start_addr, length);
      ptr = (void **)(size_t) d_n[i];
      ALIGN_OFFSET (offset, __alignof (ptr));
      cuParamSetv (function, offset, &ptr, sizeof (ptr));
      offset += sizeof (ptr);

	  data_type = records_ptr[i].getDataType ();
	  if ((data_type / 4 == 1) && (data_type % 4 == 1)) {
	  	 width_b  = *((int *)start_addr);
		 continue;
	  }
	  
	  if ((data_type / 4 == 1) && (data_type % 4 == 2)) {
	  	 height_a  = *((int *)start_addr);
		 continue;
	  }
   }

   /* Parameters have been load completely. */
   cuParamSetSize (function, offset);
   //cuFuncSetBlockShape (function, 16, 16, 1);
   //result = cuLaunchGrid (function, width_b / 16, height_a / 16);
   cuFuncSetBlockShape (function, 10, 10, 1);
   result = cuLaunchGrid (function, height_a / 10, height_a / 10);

#ifdef DEBUG
   if (result == 0)
   	  printf ("212 is normal.\n");
#endif

   /* Load data from GPU's memory to main memory. */
   for (int i = records_in; i < records_num; i++) {
      length = records_ptr[i].getLength ();
      start_addr = records_ptr[i].getStartAddr ();
      cuMemcpyDtoH ((void *) start_addr, d_n[i], length);
   }

   // Added by Zhang Yichao
   start_addr = records_ptr[records_num - 1].getStartAddr();
   *(int *)start_addr = height_a;

#ifdef DEBUG
   printf ("Here: 224\n");
   start_addr = records_ptr[records_in].getStartAddr ();
   printf ("start_addr is 0x%08x\n", start_addr);
   printf ("The first element of MatrixC is %f\n", *((float *)start_addr));
#endif

   /* Free space allocated in GPU's memory*/
   cuMemFree (d_gen);
   for (int i = 0; i < records_num; i++)
      cuMemFree (d_n[i]);

   cuEventRecord (stop, 0);
   cuEventSynchronize (stop);
   
   /* Free memory allocated in main memory for records. */
   Record::freeRecords ();
   delete d_n;
}
